<?php
namespace core\models;
/**
 * 数据库基本操作类，此类会被具体操作的数据库功能继承
 * @author  ZhouHaoHua <53027327@qq.com>
 * @modify  lwkai
 * @package 数据库
 * @see /doc/db_test/test.php
 */
class Database_Action {
	protected $db;

	/**
	 * 静态属性保存实例化的数据库对象
	 * @var Database_Action
	 * @author lwk 2013-10-22 14:11
	 */
	private static $db_result = array();
	/**
	 * 查询的字段
	 * @var string
	 */
	protected $fields = '';
	/**
	 * 用到的表
	 * @var string
	 */
	protected $tables = '';
	/**
	 * 查询条件
	 * @var string
	 */
	protected $where;
	/**
	 * 聚合信息
	 * @var string
	 */
	protected $group_by;
	/**
	 * 排序方法
	 * @var string
	 */
	protected $order_by;
	/**
	 * 取得的数据范围
	 * @var string
	 */
	protected $limit;

	/**
	 * 数据库基本操作类，不可直接调用！
	 * @param Database_Mysql $db           数据连接资源：如果不提供就用全局的，【注意】这里支持字符串或资源
	 */
	protected function __construct($db = null) {
        if (!$db) {
			$c = new \config();
			$config = $c->db();//Z_ENV
            $db = Database::open($config->type, $config->server, $config->userName, $config->password, $config->database, $config->charset);
		}
		$this->db      = $db;
	}

	public function __call($method_name, $args) {
		if (method_exists($this->db, $method_name)) {
			return call_user_func_array(array($this->db, $method_name), $args);
		}
		throw \Z::Error('方法' . __CLASS__ . '->' . $method_name . '不存在');
	}

	/**
	 * 获取insert id
	 * @return int
	 */
	function getInsertId() {
		return $this->db->insert_id();
	}

	/**
	 * 返回返回上一次执行SQL(如删除、更新、添加)操作时受到影响的条数
	 * @return number
	 */
	public function getQueryNumber() {
		return $this->db->affected_rows();
	}

    /**
     * 取得某查询列表的总记录数
     * @param array|string $table 表名
     * @param array|string $where 条件
     * @param string       $count 统计的字段，默认是*，联合表查询时要单独设置字段数据才能准确。如：o.orders_id
     * @return int 返回记录数
     * @author ZhouHaoHua <53027327@qq.com>
     * @data   2014-07-28
     */
    public function getCount($table, $where, $count ='*'){
        if(is_array($table)){
            $table = $this->db->table_array_to_string($table);
        }
        if(is_array($where)){
            $where = $this->db->where_array_to_string($where);
        }
        if($count=='*' || !$count){
            $sql = 'SELECT count(*) as Total FROM '.$table.($where ? ' WHERE '.$where : '');
        }else{
            $sql = 'SELECT count(distinct '.$count.') as Total FROM '.$table.($where ? ' WHERE '.$where : '');
        }
        $row = $this->db->fetch_array($this->db->query($sql));
        return (int)$row['Total'];
    }

	/**
	 * 静态单例调用DB的方法,注意不支持传$db对象
	 * @param String|\core\models\Database_Mysqli|\core\models\Database_Mysql $db  数据连接资源：如果不提供就用全局的，如：Database_Mysqli或Database_Mysql等
	 * @param string                                $tag 数据库连接标记
	 * @return Database_Action
	 * @throws Error
	 * @example /doc/db_test/test.php
	 */
	public static function getDb($db = null, $tag = 'zftest') {
        $_db = is_object($db) ? get_class($db) : (string)$db;
        $tag = 'db_' .$_db.'_'. $tag;
        if (empty(self::$db_result[$tag])) {
			if(is_string($db)){
                if(class_exists($db, true)){
                    $db = new $db;
                }else{
                    throw new Error('没发现此类：'.$db);
                    //die('致命错误，没发现此类：'.$db);
                }
            }
            self::$db_result[$tag] = new self($db);
		}
		return self::$db_result[$tag];
	}

	/**
	 * 添加入库，只适用对单表的插入操作。
	 * @param string $table 要添加的数据表名称
	 * @param array  $array 待插入数据的数据
	 * @return number 返回被插入的数据主键id
	 */
	public function insert($table, array $array) {
		return $this->CUD($table, $array, 'insert', '');
	}

	/**
	 * 数据表更新,只适用于更新一个表，不支持联合查询更新。
	 * @param string $table 要更新的数据表名称
	 * @param array  $data 新的数据
	 * @param string|array|int $where 更新的条件，条件字符串或只传主键值或数组。如：id=896 或 896
	 * @return number 返回被更新的记录数
	 */
	public function update($table, array $data, $where) {
		if(is_numeric($where)){
			$key_names = $this->db->table_primary_keys($table);
			if(sizeof($key_names)===1){
				$where = $key_names[0].'='.$where;
			}
			//echo $table.'|'.$where;exit;
		}elseif(is_array($where)){
			$where = $this->db->where_array_to_string($where);
		}
		return $this->CUD($table, $data, 'update', $where);
	}

    /**
     * 数据库CUD操作
     * @param string $table    要操作的数据表名称
     * @param array  $fromData 表单数据
     * @param string $type     类型，有insert:插入、update:更新、delete:删除三种
     * @param string $where    条件
     * @throws \Exception 如果$type有问题就提示异常
     * @return number
     */
	private function CUD($table, array $fromData, $type = 'insert', $where) {
		$table = (string)$table;
		if (!trim($table)) {
			throw new \Exception('没有$table！');
		}
		if (!$fromData) {
			throw new \Exception('没有$fromData！');
		}
		$formFields = $fromData;
		$num_id     = 0;
		switch ($type) {
			case 'update':
				$num_id = $this->db->fast_update($table, $where, $formFields, '*');
				break;
			case 'insert':
				$num_id = $this->db->fast_insert($table, $formFields, '');
				break;
			case 'delete':
				$num_id = $this->db->delete($table, $where);
				break;
			default:
				throw new \Exception('没有$type=' . $type . '的类型！');
				break;
		}
		return $num_id;
	}

	/**
	 * 数据库删除操作
	 * @param string $table 删除的数据表名称
	 * @param array|string $where 删除条件，可以是条件数组、条件串或主键值。如：[id => 156, key => 999] 或 id=156 或 156
	 * @throws Exception
	 * @return number 返回被删除的记录数
	 */
	public function delete($table, $where) {
		$rtn = 0;
		if(is_numeric($where)){
			$key_names = $this->db->table_primary_keys($table);
			if(sizeof($key_names)==1){
				$where = $key_names[0].'='.$where;
			}else{
				throw \Z::Error('不支持多主键的情况！');
			}
		}elseif(is_array($where)){
			$where = $this->db->where_array_to_string($where);
		}
		if (!trim($where) || is_numeric($where)) {
			throw \Z::Error('没有删除条件，很危险的事情！');
		}else{
			$rtn = $this->db->delete($table, $where);
		}
		return $rtn;
	}

	/**
	 * 取得某个数据表的其中一条信息
	 * @param string|array     $table_name 表名，若是数组的话key应是表别名，val是表名
	 * @param string|int|array $where      条件 可以是主键值或条件字符串或数组
	 * @param string     $field
	 * @return array
	 */
	public function getOne($table_name, $where = ' 1 ', $field = '*') {
		if ($field == '*') {
            return $this->db->get_one_row($table_name, $where);
		} else {
			return $this->db->get_row($table_name, $field, $where);
		}
	}


	/**
	 * 取得数据列表
	 * @return array 返回数组
	 */
	public function getList() {
		//throw new Exception('各个功能块要列的表可能不一样，建议在各功能块定义列表！');
		$sql = $this->setSql();
		//echo $sql;
		$query = $this->db->query($sql);
		$data  = array();
		while ($rows = $this->db->fetch_array($query)) {
			$data[] = $rows;
		}
		$this->db->free_result($query);
		return $data;
	}

	/**
	 * 取得带分页功能的数据列表
	 * @param int     $maxRowsEveryPage    每页显示几条数据？
	 * @param string  $pageHolder          分页的变量参数名 如$_GET['pageA']就在此填pageA
	 * @param int     $maxDisplayPageLinks 每页面最多显示多少个页码？
	 * @param boolean $forSeo              是否启用seo模式，默认为是
	 * @param string  $countField          统计总数的字段名，默认为*。注意：如果包括有group by等语句时请务必使用真实的字段而不是用*号，如：p.t_id
	 * @param string  $addParams           额外要带的参数字符串，如本函数默认会去掉action,x,y这三个参数，如果你希望其中某个参数不去掉，则在这里带上，或者加上额外的其他参数
	 * @return array 返回数组
	 */
	public function getPageList($maxRowsEveryPage = 4, $pageHolder = 'page', $maxDisplayPageLinks = 6, $forSeo = true, $countField = '*', $addParams = '') {
		//throw new Exception('各个功能块要列的表可能不一样，建议在各功能块定义列表！');
		$listing_sql = $this->setSql();
		//echo $listing_sql;
		$listing_split = new splitPageResults($listing_sql, $maxRowsEveryPage, $countField, $pageHolder, $this->db);
		$data          = array();
		if ($listing_split->number_of_rows > 0) {
			//分页数据信息
			//if (defined('IS_BACKSTAGE') && IS_BACKSTAGE === true) { //系统后台的分页方式
				//$data['pages']['displayPages'] = $listing_split->display_links($maxDisplayPageLinks, Html::get_url_params(array($pageHolder,'x','y')) . '&' . $addParams);//'action',
				//$data['pages']['displayCount'] = $listing_split->display_count('');
			//} else { //前台的分页方式
				$data['pages']                = $listing_split->display_links_array($maxDisplayPageLinks, Html::get_url_params(array($pageHolder,'x','y')) . '&' . $addParams, $forSeo);//'action',
				$data['pages']['totalNumber'] = $listing_split->number_of_rows;
				$data['pages']['totalpages']  = $listing_split->number_of_pages;
				$data['pages']['currentPage'] = $listing_split->current_page_number;
				//$data['pages']['rowsStatistics'] = $listing_split->display_count(TEXT_DISPLAY_NUMBER_OF_ORDERS);
				$data['pages']['rowsStatistics'] = $listing_split->getResultsStatistics();
			//}
			//列表的数据信息
			$data['list']  = array();
			$listing_query = $this->db->query($listing_split->sql_query);
			$i             = 0;
			while ($listing = $this->db->fetch_array($listing_query)) {
				$data['list'][$i] = $listing;
				$i++;
			}
			$this->db->free_result($listing_query);
		}
		return $data;
	}

	/**
	 * 快速取得某表中某个字段的值，支持多表接连
	 * @param string       $field_name 字段名称
	 * @param string|array $table_name 数据表名称
	 * @param string|array $where 查询条件(不含where字符)，可以是条件串或主键或数组。如：id=103 或 103 或 array('id'=>103)
	 *
	 * @throws Exception_User
	 * @return string|integer
	 */
	public function getFieldValue($field_name, $table_name, $where) {
		if(is_array($table_name) && is_numeric($where)){
			throw new Exception_User(print_r($table_name,true).'是数组，不能和数字的条件'.$where.'使用！');
		}
		if(is_numeric($where)){
			$key_names = $this->db->table_primary_keys($table_name);
			if(sizeof($key_names)===1){
				$where = $key_names[0].'='.$where;
			}else{
				throw new Exception_User($table_name.'主键必须为1个才能用数字变量');
			}
		}elseif(is_array($where)){
			$where = $this->db->where_array_to_string($where);
		}
		if(is_array($table_name)){
			$table_name = $this->db->table_array_to_string($table_name);
		}

		return $this->db->get_field_value($field_name, $table_name, $where);
	}

    /**
     * SQL 查询语句格式化
     * @param string $tableStr  表名，如table_0 as t0, table_1 as t1
     * @param string $whereStr  条件 t0.id=t1.id
     * @param string $fieldsStr 字段名称，默认是*
     * @param string $groupBy   聚合内容，默认为空。如：t0.id
     * @param string $orderBy   排序方式，默认为空。如：t0.id ASC
     * @param string $Limit     取得几条。支持：0, 100或100的写法
     * @return string
     */
	protected function selectSqlFormat($tableStr, $whereStr, $fieldsStr = '*', $groupBy = '', $orderBy = '', $Limit = '') {
		return $this->db->formatSelectStr($tableStr, $whereStr, $fieldsStr, $groupBy, $orderBy, $Limit);
	}

	/**
	 * 数据库输入前的格式化
	 * @see Database_Mysql::input()
	 * @param $data
	 * @return string|array
	 */
	final public function input($data) {
		if (is_array($data)) {
			foreach ($data as $key => $val) {
				if (is_array($val)) {
					$data[$key] = $this->input($val);
				} elseif (is_string($val) && trim($val) != '') {
					$data[$key] = $this->db->input($val);
				}
			}
		} elseif (is_string($data) && trim($data) != '') {
			return $this->db->input($data);
		}
		return $data;
	}

	/**
	 * 数据资料安全输出
	 * @see Database_Mysql::output()
	 * @param string|array $data 待转码的数据
	 * @return string|array
	 */
	final public function safeOutput($data) {
		return $this->db->output($data);
	}

	/**
	 * 将数据资料转成可安全输入数据库的格式
	 * @param string $data 待转码的数据
	 * @return string 返回格式化后的字符串
	 */
	final public function safeInput($data) {
		return $this->db->input($this->db->prepare_input($data));
	}

	/**
	 * 添加要操作的表格
	 * 注意：$name若是数组格式时，key是表的别名，val是表名
	 * @param string|array $name 表名【支持字符串和数组】。如tables_1 或 tables_1 as t1 或 tables_1 as t1, tables_2 as t2 或 array('t1'=>'tables_1','t2'=>'tables_2')
	 * @return $this
	 */
	public function addTable($name) {
		if(is_array($name)){
			$name = $this->db->table_array_to_string($name);
		}
		if ($name) {
			$this->tables .= ($this->tables ? ', ' : '') . $name;
		}
		return $this;
	}

	/**
	 * 添加查询的字段
	 * @param array $fieldName 如：*,或a.*或a.field, b.field, c.field 或 array("a.field", "b.field", "c.field") 或 array("a.field" => 'asF1', "b.field" => 'asF2', "c.field" => 'asF3')
	 * @return $this
	 */
	public function addField($fieldName) {
		if (is_array($fieldName)) {
			$arr = array();
			foreach ($fieldName as $k => $v) {
				if(!is_int($k)){
					$arr []= $k.' AS '.$v;
				}else{
					$arr []= $v;
				}
			}
			//$fieldName = implode(', ', $fieldName);
			$fieldName = implode(', ', $arr);
		}
		$fields = (trim($this->fields) ? ', ' : '') . $fieldName;
		$this->fields .= $fields;
		return $this;
	}

	/**
	 * 添加条件
	 * @param int|string|array $whereString 条件串，支持一维数组和字符串或数字型主键 如：1或id=1或id=1 and name="ZhouHaoHua <53027327@qq.com>" or name="Zhou" 或 array('id'=>123, 'name'=>'zhouhaohua')
	 * @return $this
	 * @throws \Exception
	 */
	public function addWhere($whereString) {
		if(is_array($whereString)){
			$whereString = $this->db->where_array_to_string($whereString);
		}elseif(is_numeric($whereString)){
			$_t = trim($this->tables);
			if(!$_t){
				throw new \Exception('必须先指定表名');
			}
			if(sizeof(preg_split('/[\s,]+/',$_t)) < 2){ //采用传主键值方式只能用于单表查询
				$key_names = $this->db->table_primary_keys($this->tables);
				if(sizeof($key_names)===1){
					$whereString = $key_names[0].'='.$whereString;
				}
			}
		}

		$this->where .= $whereString;

		return $this;
	}

	/**
	 * 添加Group内容
	 * @param string $groupString 如：agents_id或agents_id, agents_name
	 * @return $this
	 */
	public function addGroupBy($groupString) {
		$this->group_by .= (trim($this->group_by) ? ', ' : '') . $groupString;
		return $this;
	}

	/**
	 * 添加OrderBy内容
	 * @param string $orderString 如：a.id ASC 或 a.id ASC, b.name DESC
	 * @return $this
	 */
	public function addOrderBy($orderString) {
		$this->order_by .= (trim($this->order_by) ? ', ' : '') . $orderString;
		return $this;
	}

	/**
	 * 添加Limit内容
	 * @param string $limitString 如：1000或 100,199
	 * @return $this
	 */
	public function addLimit($limitString) {
		$this->limit = $limitString;
		return $this;
	}

	/**
	 * 拼接出完整的SQL语句
	 */
	final public function setSql() {
		$full_sql = $this->db->formatSelectStr($this->tables, $this->where, $this->fields, $this->group_by, $this->order_by, $this->limit);
		$this->clear();
		return $full_sql;
	}

    /**
     * 显示完整的SQL查询，用于测试时用
     * @example $this->addField('*')->addTable($this->table)->addWhere($where)->addOrderBy($order_by)->showSql()
     * @param bool $show 是否直接显示，如果为true就直接在当前位置显示出来。
     * @param string $s 此值可以保存SQL语句到外面显示
     * @return $this
     */
	final public function showSql($show = true, &$s = '') {
        $s = $this->db->formatSelectStr($this->tables, $this->where, $this->fields, $this->group_by, $this->order_by, $this->limit)."<br>\n";
        if($show == true){
			echo $s;
		}
		return $this;
		//exit;
	}

	/**
	 * 释放查询语句变量
	 */
	final public function clear() {
		$this->tables   = '';
		$this->fields   = '';
		$this->where    = '';
		$this->group_by = '';
		$this->order_by = '';
		$this->limit    = '';
	}

	/**
	 * 执行一个完整的SQL语句查询
	 * @param string $sql_str
	 * @return resource
	 */
	public function query($sql_str) {
		return $this->db->query($sql_str);
	}

    /**
     * @param resource $query
     * @return array|mixed
     */
	public function fetch_array($query) {
		return $this->db->fetch_array($query);
	}

    /**
     * 有时候为了把一个条件数组，预先转换为一个条件字符串，所以需要用到此方法
     * @param array  $where 条件数组
     * @param string $relationship_symbol 关系符号(AND  OR)目前只支持这两个
     * @return string
     * @author lwkai
     * @date   2016-8-4
     */
    public function where_array_to_string(array $where,$relationship_symbol = 'AND'){
        return $this->db->where_array_to_string($where,$relationship_symbol);
    }

	/**
	 * 格式化输出某个字段的名称
	 * @param string $name (引用)输出的名称是经过安全过滤后的字符串
	 */
	public function formatFieldName(&$name) {
		$name = preg_replace('/[^a-zA-Z0-9_]/', '', $name);
	}

    /**
     * 判断某个表是否存在
     * @param string $table_name 表名
     * @return bool
     */
    public function table_exists($table_name){
        return $this->db->table_exists($table_name);
    }
}
